package com.blackto.lib.mediat.core;

import com.blackto.lib.mediat.exception.HandlerNoFoundException;
import com.blackto.lib.mediat.multicast.INotification;
import com.blackto.lib.mediat.multicast.INotificationHandler;
import com.blackto.lib.mediat.unicast.IRequest;
import com.blackto.lib.mediat.unicast.IRequestHandler;
import org.springframework.context.ApplicationContext;
import org.springframework.core.ResolvableType;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

/**
 * 中介者模式实现类，参考MediatR
 */
public class Mediator implements IMediator{
    private final ApplicationContext context;

    public Mediator(ApplicationContext context) {
        this.context = context;
    }

    /**
     * 广播发布，将notification发送给所有相关handler，
     * 依次调用每个handler的处理方法
     *
     * 注意：该方法为同步方法，会等所有handler的handle方法依次执行完才返回
     * @param notification
     * @param <TNotification>
     */
    @Override
    public <TNotification extends INotification> void publish(TNotification notification) {
        // 获取对应的handler的类型：INotificationHandler<TNotification>
        ResolvableType handlerType = ResolvableType.forClassWithGenerics(
                INotificationHandler.class, notification.getClass());
        // 获取所有对应handler的名字
        String[] handlerNames = context.getBeanNamesForType(handlerType);
        List<INotificationHandler<TNotification>> handlers = new ArrayList<>(handlerNames.length);
        // 获取所有handler实例
        for (String handlerName : handlerNames)
            handlers.add((INotificationHandler<TNotification>) context.getBean(handlerName));
        // 执行方法
        handlers.parallelStream().forEach( handler -> {
            try {
                handler.handle(notification);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

    /**
     * 点对点单播，将request发给对应handler，调用其处理方法
     * 若实现了多个handler，取第一个handler
     * @param request
     * @param <TResponse>
     * @return
     */
    @Override
    public <TResponse> TResponse send(IRequest<TResponse> request) {
        // 首先根据请求获取返回值类型
        Type[] genericInterfaces = request.getClass().getGenericInterfaces();

        Type responseType = null;

        for (Type type : genericInterfaces){
            if (type instanceof ParameterizedType) {
                ParameterizedType parameterizedType = (ParameterizedType) type;
                if (!parameterizedType.getRawType().equals(IRequest.class)) {
                    continue;
                }
                responseType = parameterizedType.getActualTypeArguments()[0];
                break;
            }
        }

        if (responseType == null) {
            throw new HandlerNoFoundException(request.getClass());
        }

        Class<?> requestClass = request.getClass();

        Class<?> responseClass = null;
        // 获取返回值类型
        if (responseType instanceof ParameterizedType) {
            String className = ((ParameterizedType) responseType).getRawType().getTypeName();
            try {
                responseClass = Class.forName(className);
            } catch (ClassNotFoundException e) {
                responseClass = Object.class;
            }
        } else {
            responseClass = (Class<?>) responseType;
        }

        // 根据获得的类型，获取具体的handler类型
        ResolvableType handlerType = ResolvableType.forClassWithGenerics(
                IRequestHandler.class,
                requestClass,
                responseClass);

        String[] handlerNames = this.context.getBeanNamesForType(handlerType);
        List<IRequestHandler<IRequest<TResponse>, TResponse>> handlers = new ArrayList<>(handlerNames.length);
        for (String handlerName :
                handlerNames) {
            handlers.add((IRequestHandler<IRequest<TResponse>, TResponse>) this.context.getBean(handlerName));
        }

        if(handlers.get(0) == null){
            throw new HandlerNoFoundException(request.getClass());
        }
        // 处理
        try {
            return handlers.get(0).handle(request);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}
